﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Threading;
using FRW.Common.Extensions;
using FRW.Common.Configuration;
using FRW.Common.FileSystem;

namespace FileRenamer
{
    public delegate void UpdateProgressDelegate(int percentage);
    public delegate void CompleteDelegate();

    public class TheDoc
    {
        private UpdateProgressDelegate ProgressDelegate;
        private ActionItems m_actions = new ActionItems();
        private ActionItems m_filteredActions;
        private Logger _logger = new Logger();
        private Configuration<Configuration> _configuration;

        public bool ActionCancel = false;
        public bool ActionFinished = true;
        public Logger Logger
        {
            get { return _logger; }
        }
        public Configuration Configuration
        {
            get
            {
                if (_configuration != null)
                {
                    return _configuration.Item;
                }
                else
                {
                    return null;
                }
            }
        }
        public CompleteDelegate CompleteHandler { get; set; }
        public ActionItems Actions
        {
            get
            {
                if (m_filteredActions != null)
                {
                    return m_filteredActions;
                }
                else
                {
                    return m_actions;
                }
            }
        }


        #region Constructor
        public TheDoc()
        {
        }
        #endregion

        #region Configuration
        public void Load(string fileName)
        {
            LoadConfiguration(fileName);
        }

        public void Save(string fileName)
        {
            SaveConfiguration(fileName);
        }

        private void LoadConfiguration(string fileName = null)
        {
            _configuration = new Configuration<Configuration>();
            if (!_configuration.Load(fileName))
            {
                _configuration.LoadDefault();
            }
        }

        private void SaveConfiguration(string fileName)
        {
            _configuration.Save(fileName);
        }
        #endregion


        #region Handler Threads
        public void DoActions(object o)
        {
            int totalFiles = m_actions.Count;
            int currentFileCount = 0;

            ActionFinished = false;

            foreach (ActionItem action in m_actions)
            {
                if (ActionCancel == true)
                {
                    break;
                }

                action.LogText = "";
                _logger.StartNewLogger();

                currentFileCount++;

                // TODO we could maybe update this to call the delegate centrally based on a time tick rather than every action
                if (ProgressDelegate != null)
                {
                    ProgressDelegate(100 * currentFileCount / totalFiles);
                }

                switch (action.Action)
                {
                    case ActionItem.ActionType.MOVE:
                        _logger.Log("Move", MessageType.INFORMATION, Color.Blue, false);
                        _logger.Log(" - " + action.Source + " to " + action.Destination, MessageType.INFORMATION, Color.Green, true);
                        if (!_configuration.Item.Overwrite && File.Exists(action.Destination))
                        {
                            _logger.Log("Error file exists!", MessageType.ERROR, Color.Red, true);
                            action.Action = ActionItem.ActionType.ERROR;
                        }
                        else
                        {
                            try
                            {
                                if (_configuration.Item.Debug != true)
                                {
                                    // TODO need to handle case where file exists as this causes an exception (only appears to be on Windows 7?), delete and then copy???
                                    File.Move(action.Source, action.Destination);
                                    _logger.Log("Moved", MessageType.INFORMATION, Color.Green, true);
                                }
                                else
                                {
                                    _logger.Log("Debug moved", MessageType.INFORMATION, Color.Green, true);
                                }
                                action.Action = ActionItem.ActionType.MOVED;
                            }
                            catch (Exception ex)
                            {
                                _logger.Log("Cannot move file! - " + ex.Message, MessageType.ERROR, Color.Red, true);
                                action.Action = ActionItem.ActionType.ERROR;
                            }
                        }
                        break;

                    case ActionItem.ActionType.COPY:
                        action.Action = ActionItem.ActionType.COPIED;
                        break;

                    case ActionItem.ActionType.IGNORE:
                    case ActionItem.ActionType.NOCHANGE:
                        break;

                    default:
                        break;
                }

                action.LogText = _logger.GetLoggerText();
            }

            ActionFinished = true;
            CompleteHandler();
        }

        public void DoParseFiles(object o)
        {
            int totalFiles = m_actions.Count;
            int currentFileCount = 0;

            ActionFinished = false;

            foreach (ActionItem action in m_actions)
            {
                if (ActionCancel == true)
                {
                    break;
                }
                // TODO we may also need to clear out the current text of the action string as appending new text does not appear to work???
                action.LogText = "";
                _logger.StartNewLogger();
                _logger.Log("Parsing - " + action.Source, MessageType.DEBUG, Color.Black, true);

                currentFileCount++;
                if (ProgressDelegate != null)
                {
                    ProgressDelegate(100 * currentFileCount / totalFiles);
                }

                FileInfo fi = new FileInfo(action.Source);

                // action.Destination = action.Source;

                string name = fi.Name;
                string folder = fi.DirectoryName;
                string extension = fi.Extension;
                string baseName = name.Replace(extension, "");
                extension = extension.Replace(".", "");

                // parse dates that may be in file name and update to a standard format if required.
                List<RegReplace> dateExpressions = _configuration.Item.ExpressionList.Where(expression => expression.Type == RegReplace.RegExpType.DATE).Select(exp => exp).ToList();
                List<RegReplace> ignoreExpressions = _configuration.Item.ExpressionList.Where(expression => expression.Type == RegReplace.RegExpType.IGNORE).Select(exp => exp).ToList();
                List<RegReplace> replaceExpressions = _configuration.Item.ExpressionList.Where(expression => expression.Type == RegReplace.RegExpType.REPLACE).Select(exp => exp).ToList();

                if (IsFileIgnored(ignoreExpressions, baseName))
                {
                    action.Action = ActionItem.ActionType.IGNORE;
                }
                else
                {

                    foreach (RegReplace expression in dateExpressions)
                    {
                        baseName = FindDateInString(baseName, expression.RegExp, expression.RepString);
                    }

                    // now sort out what the filename should be based on applying the regular expression rules
                    baseName = UpdateFileName(action, baseName);

                    // check if the file should be moved under the destination folder or remane where it is
                    if (_configuration.Item.Move == true && action.Action != ActionItem.ActionType.IGNORE)
                    {
                        if (_configuration.Item.MatchDestinationFolder)
                        {
                            DirectoryStructure destinationStructure = new DirectoryStructure(_configuration.Item.DestinationDir);
                            string destinationFolder = null;
                            string updatedName = null;
                            int maxMatchLength = 0;

                            foreach (DestinationDirectoryMatch data in destinationStructure.DirectoryLocations(_configuration.Item.DestinationDir))
                            {
                                if (Regex.IsMatch(baseName, "^" + data.RegularExpression))
                                {
                                    _logger.Log("Directory match - ", MessageType.DEBUG, Color.Green, false);
                                    _logger.Log(baseName + " - " + data.Folder, MessageType.DEBUG, Color.Orange, true);
                                    
                                    // check that this folder length is greater than any previous match we may have made
                                    if (data.Folder.Length > maxMatchLength)
                                    {
                                        if (_configuration.Item.StripFolderName)
                                        {
                                            updatedName = Regex.Replace(baseName, "^" + data.RegularExpression, "", RegexOptions.IgnoreCase);
                                            _logger.Log("Update name - ", MessageType.DEBUG, Color.Green, false);
                                            _logger.Log(baseName + " - " + updatedName, MessageType.DEBUG, Color.OrangeRed, true);
                                        }

                                        _logger.Log("Update directory - ", MessageType.DEBUG, Color.Green, false);
                                        _logger.Log(data.Folder, MessageType.DEBUG, Color.YellowGreen, true);

                                        destinationFolder = data.Folder;
                                        maxMatchLength = data.Folder.Length;
                                    }
                                }
                            }

                            string destinationPath = null;
                            string destinationName = null;

                            // if we reach here and destination has not been updated then we need to set it to default output directory
                            if (String.IsNullOrWhiteSpace(destinationFolder))
                            {
                                if (Regex.IsMatch(_configuration.Item.DestinationDir, "\\\\$"))
                                {
                                    destinationPath = _configuration.Item.DestinationDir;
                                }
                                else
                                {
                                    destinationPath = _configuration.Item.DestinationDir + "\\";
                                }

                                destinationName = baseName + "." + extension;
                                _logger.Log("Directory match - ", MessageType.DEBUG, Color.Green, false); 
                                _logger.Log("we did not find a suitable location so save to default", MessageType.DEBUG, Color.Orange, true);
                            }
                            else
                            {
                                if (String.IsNullOrWhiteSpace(updatedName))
                                {
                                    destinationPath = destinationFolder;
                                    destinationName = baseName + "." + extension;
                                }
                                else
                                {
                                    destinationPath = destinationFolder;
                                    destinationName = updatedName + "." + extension;
                                }
                            }

                            // TODO add a check to prevent files being moved back in the folder structure maybe because the short filename was used and some directory info is stripped
                            if (action.Source.Contains(destinationPath))
                            {
                                FileInfo sourceFile = new FileInfo(action.Source);

                                destinationPath = sourceFile.DirectoryName + "\\";

                                _logger.Log("Destination is subset of source so keep source path", MessageType.DEBUG, Color.Red, true);
                            }

                            action.Destination = destinationPath + destinationName;
                        }
                        else
                        {
                            action.Destination = _configuration.Item.DestinationDir + "\\" + baseName + "." + extension;
                        }
                    }
                    else
                    {
                        if (action.Action != ActionItem.ActionType.IGNORE)
                        {
                            action.Destination = folder + @"\" + baseName + "." + extension;
                        }
                    }

                    // check if the location or filename has changed if not we can ignore
                    if (String.Compare(action.Source, action.Destination, true) == 0)
                    {
                        action.Action = ActionItem.ActionType.NOCHANGE;
                    }
                    else
                    {
                        if (action.Action == ActionItem.ActionType.SCANNED)
                        {
                            action.Action = ActionItem.ActionType.MOVE;
                        }
                    }
                }
                action.LogText = _logger.GetLoggerText();
            }
            ActionFinished = true;
            CompleteHandler();
        }

        public void DoScanFiles(object o)
        {
            DirectoryInfo sdi = new DirectoryInfo(_configuration.Item.SourceDir);

            if (!sdi.Exists)
            {
                throw new DirectoryNotFoundException();
            }

            DirectoryStructure ds = new DirectoryStructure(_configuration.Item.SourceDir);
            ds.Recursive = _configuration.Item.Recursive;

            // get recursive list of files
            m_actions = new ActionItems();
            List<String> fileList = new List<string>();

            ActionFinished = false;

            int totalFiles = ds.Files.Count();
            int currentFileCount = 0;


            foreach (FileInfo fi in ds.Files)
            {
                currentFileCount++;

                if (ActionCancel == true)
                {
                    break;
                }

                if (ProgressDelegate != null)
                {
                    ProgressDelegate(100 * currentFileCount / totalFiles);
                }

                string extension = fi.Extension.Replace(".", "");
                if (_configuration.Item.Extensions.Contains(extension.ToLower()))
                {
                    m_actions.Add(new ActionItem(fi.FullName, "", ActionItem.ActionType.SCANNED));
                }
            }

            ActionFinished = true;
            CompleteHandler();
        }
        #endregion

        #region Filters and Sorting
        public void ClearFilters()
        {
            m_filteredActions = null;
        }

        public void FilterActionsByAction(string action)
        {
            var actionItems = from actionItem in m_actions where actionItem.Action.ToString().Equals(action) select actionItem;

            m_filteredActions = new ActionItems();

            foreach (ActionItem actionItem in actionItems)
            {
                m_filteredActions.Add(actionItem);
            }
        }

        public void OrderRulesByPriority()
        {
            var dataList = from data in _configuration.Item.ExpressionList orderby data.Priority descending select data;

            List<RegReplace> tempList = dataList.ToList();

            _configuration.Item.ExpressionList.Clear();

            foreach (RegReplace expression in tempList)
            {
                _configuration.Item.ExpressionList.Add(expression);
            }

        }

        #endregion

        #region Action Handlers
        public void ActionScanFiles(UpdateProgressDelegate progressDelegate)
        {
            ProgressDelegate = progressDelegate;

            Thread scanThread = new Thread(DoScanFiles);

            ActionCancel = false;
            scanThread.Start();
        }

        public void ActionParseFiles(UpdateProgressDelegate progressDelegate)
        {
            ProgressDelegate = progressDelegate;

            Thread parseThread = new Thread(DoParseFiles);

            ActionCancel = false;
            parseThread.Start();
        }

        public void ActionMoveFiles(UpdateProgressDelegate progressDelegate)
        {
            ProgressDelegate = progressDelegate;

            Thread actionThread = new Thread(DoActions);

            ActionCancel = false;
            actionThread.Start();
        }
        #endregion

        public void DeleteFiles(ActionItems actions)
        {
            foreach (ActionItem action in actions)
            {
                if (File.Exists(action.Source))
                {
                    try
                    {
                        File.Delete(action.Source);
                        action.Action = ActionItem.ActionType.DELETED;
                        _logger.Log("Deleted", MessageType.INFORMATION, Color.Green, true);
                    }
                    catch (Exception ex)
                    {
                        action.Action = ActionItem.ActionType.ERROR;
                        _logger.Log("Delete Error - " + ex.Message, MessageType.ERROR, Color.Red, true);
                    }
                }
                else
                {
                    action.Action = ActionItem.ActionType.ERROR;
                    _logger.Log("Source Missing", MessageType.ERROR, Color.Red, true);
                }
            }
        }

        #region File Name Parsers

        private bool IsFileIgnored(IEnumerable<RegReplace> ignoreExpressions, string fileName)
        {
            bool ignoreFile = false;

            foreach (RegReplace reg in ignoreExpressions)
            {
                if (reg.Type != RegReplace.RegExpType.IGNORE)
                {
                    continue;
                }

                if (Regex.IsMatch(fileName, reg.RegExp, RegexOptions.IgnoreCase))
                {
                    ignoreFile = true;
                    _logger.Log("IGNORED - (" + reg.RegExp + ") - ", MessageType.DEBUG, Color.Blue, false);
                    _logger.Log(fileName, MessageType.DEBUG, Color.Green, true);
                    break;
                }
            }
            
            return ignoreFile;
        }

        private string UpdateFileName(ActionItem action, string baseName)
        {
            var replExpressions = _configuration.Item.ExpressionList.Where(expression => expression.Type != RegReplace.RegExpType.DATE).Select(exp => exp);

            // check the file name against each of the configured regular expressions
            foreach (RegReplace reg in replExpressions)
            {
                if (Regex.IsMatch(baseName, reg.RegExp, RegexOptions.IgnoreCase))
                {
                    switch (reg.Type)
                    {
                        case RegReplace.RegExpType.REPLACE:
                            if (reg.RepString == null)
                            {
                                reg.RepString = "";
                            }
                            baseName = Regex.Replace(baseName, reg.RegExp, reg.RepString, RegexOptions.IgnoreCase);
                            _logger.Log("(" + reg.RegExp + ") - (" + reg.RepString + ") - ", MessageType.DEBUG, Color.Blue, false);
                            _logger.Log(baseName, MessageType.DEBUG, Color.Green, true);
                            action.Action = ActionItem.ActionType.MOVE;
                            break;
                    }
                }

                // if type ever goes to ignore then we can quit out as we should ignore the file
                if (action.Action == ActionItem.ActionType.IGNORE)
                {
                    break;
                }
            }
            return baseName;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="str"></param>
        /// <param name="regDate"></param>
        /// <param name="repDate"></param>
        /// <returns></returns>
        private string FindDateInString(string str, string regDate, string repDate)
        {
            string[] dateFormat = repDate.Split('|');
            string replacementFormat;
            string displayDateFormat = "d";
            if (dateFormat.Count() == 1)
            {
                replacementFormat = dateFormat[0];
            }
            else if (dateFormat.Count() == 2)
            {
                replacementFormat = dateFormat[0];
                displayDateFormat = dateFormat[1];
            }
            else
            {
                MessageBox.Show("Wrong format for replacement date string - " + repDate);
                return str;
            }


            if (Regex.IsMatch(str, regDate, RegexOptions.IgnoreCase))
            {
                Match match = Regex.Match(str, regDate);
                string possibleDate = match.Value;
                string newDate;
                possibleDate = Regex.Replace(possibleDate, regDate, replacementFormat);
                try
                {
                    DateTime date = Convert.ToDateTime(possibleDate);
                    newDate = string.Format(" - " + displayDateFormat, date);
                    _logger.Log("Parsed date - " + newDate, MessageType.INFORMATION, Color.Orange, true);
                }
                catch
                {
                    _logger.Log("Error parsing date - " + possibleDate, MessageType.DEBUG, Color.Red, true);
                    return str;
                }

                str = Regex.Replace(str, regDate, newDate);
            }

            return str;
        }
        #endregion
    }
}
